<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <title></title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <p>
      <input type="text" name="url" id="url" />
      <button id="add" type="button">Add</button>
      <button id="replace" type="button">Replace</button>
    </p>
    <script>
      const defaultBase = '#!';
      const reg = /\{([^{]+)\}/g;
      const regForSplit = /\{[^{]+\}/;
      const escapeReg = /[\^\$\\\.\*\+\?\(\)\[\]\{\}\|]/g;
      function regexpEscape(str) {
        return str.replace(escapeReg, '\\$&');
      }
      function Rule() {
        this.rules = {};
        this.otherwise = function() {};
        this.on = function(url, func) {
          const params = [];
          const arr = url.split(regForSplit);
          let result = arr.map((p) => {
            return regexpEscape(encodeURI(p));
          }).join('([^/?]+?)');
          result += '$';
          url.replace(reg, (m, m1) => {
            params.push(m1);
            return m;
          });
          this.rules[result] = { params: params, event: func };
        };
        this.trigger = function(url) {
          const regexs = Object.keys(this.rules);
          let count = 0;
          regexs.forEach((reg) => {
            const regObj = new RegExp(reg);
            const match = regObj.exec(url);
            if (match && match[0]) {
              const rule = this.rules[reg];
              const paramObj = {};
              rule.params.forEach((param, index)=>{
                paramObj[param] = match[index += 1];
              });
              rule.event(paramObj);
              count++;
            }
          });
          if (count === 0) {
            this.otherwise();
          }
        };
      }
      function getRealPath(hash) {
        const index = hash.indexOf(defaultBase);
        if (index >= 0) {
          return hash.slice(index + defaultBase.length);
        }
        return hash;
      }
      const rules = new Rule();
      function hashChangeHandler() {
        const url = getRealPath(location.hash);
        rules.trigger(encodeURI(url));
      }
      
      
      const route = {
        when: function(url, func) {
          if (url.indexOf(defaultBase) >= 0) throw new Error(`Can not use ${defaultBase}`);
          rules.on(url, func);
          return this;
        },
        otherwise: function(func) {
          rules.otherwise = func;
          if (window.history.length === 1) {
            hashChangeHandler();
          }
          return this;
        }
      };
      window.onhashchange = hashChangeHandler;

      route
      .when('hello/{id}={x}', function(param){
        console.log(param.id, param.x);
      })
      .when('{hello}?中', function(param) {
        console.log(param['hello']);
      })
      .otherwise(function(){
        console.log('otherwise');
      });
    </script> 
  </body>
</html>